﻿/**
 * FFXIIRCGateway
 *
 * @package ffxiircgateway
 * @version $Id: IRCClient.cs 22 2009-05-10 02:17:33Z ayunyan $
 * @author ayunyan <ayu@commun.jp>
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
 * @link http://code.google.com/p/ffxiircgateway
 */
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Net.Sockets;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Runtime.InteropServices;

namespace FFXIIRCGateway
{
    public class IRCClient
    {
        private ISynchronizeInvoke syncObject;
        private Socket sock;
        private string nickName;
        private string userName;
        private string realName;
        private string serverPassword;
        private string nickservPassword;

        public delegate void ConnectedEventHandler(object sender);
        public event ConnectedEventHandler Connected;
        public delegate void DisconnectedEventHandler(object sender);
        public event DisconnectedEventHandler Disconnected;
        public delegate void ConnectFailedEventHandler(object sender, string message);
        public event ConnectFailedEventHandler ConnectFailed;
        public delegate void ExceptionEventHandler(object sender, Exception e);
        public event ExceptionEventHandler Exception;
        public delegate void LoginCompleteEventHandler(object sender);
        public event LoginCompleteEventHandler LoginComplete;
        public delegate void PRIVMSGEventHandler(object sender, IRCMessage message);
        public event PRIVMSGEventHandler PRIVMSG;
        public delegate void NOTICEEventHandler(object sender, IRCMessage message);
        public event NOTICEEventHandler NOTICE;
        public delegate void CTCPEventHandler(object sender, IRCMessage message);
        public event CTCPEventHandler CTCP;
        public delegate void JOINEventHandler(object sender, IRCMessage message);
        public event JOINEventHandler JOIN;
        public delegate void PARTEventHandler(object sender, IRCMessage message);
        public event PARTEventHandler PART;
        public delegate void QUITEventHandler(object sender, IRCMessage message);
        public event QUITEventHandler QUIT;
        public delegate void MODEEventHandler(object sender, IRCMessage message);
        public event MODEEventHandler MODE;
        public delegate void TOPICEventHandler(object sender, IRCMessage message);
        public event TOPICEventHandler TOPIC;
        public delegate void NICKEventHandler(object sender, IRCMessage message);
        public event NICKEventHandler NICK;
        public delegate void INVITEEventHandler(object sender, IRCMessage message);
        public event INVITEEventHandler INVITE;

        public IRCClient()
        {
            this.nickName = "";
            this.userName = "";
            this.realName = "";
            this.serverPassword = "";
            this.nickservPassword = "";
        }

        public IRCClient(ISynchronizeInvoke iSyncObject)
        {
            this.syncObject = iSyncObject;

            this.nickName = "";
            this.userName = "";
            this.realName = "";
            this.serverPassword = "";
            this.nickservPassword = "";
        }

        public void Connect(string host, int port, string serverPassword, string nickname, string username, string realname, string nickservPassword)
        {
            this.serverPassword = serverPassword;
            this.nickservPassword = nickservPassword;
            this.Connect(host, port, nickname, username, realname);
        }

        public void Connect(string host, int port, string serverPassword, string nickname, string username, string realname)
        {
            this.serverPassword = serverPassword;
            this.Connect(host, port, nickname, username, realname);
        }

        public void Connect(string host, int port, string nickname, string username, string realname)
        {
            this.nickName = nickname;
            this.userName = username;
            this.realName = realname;
            this.sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            this.sock.BeginConnect(host, port, new AsyncCallback(ConnectCallback), this.sock);
        }

        private void ConnectCallback(IAsyncResult ar)
        {
            try
            {
                this.sock.EndConnect(ar);

                this.OnConnected();
                this.Login();
                this.Receive();
            }
            catch (Exception e)
            {
                this.OnConnectFailed(e.Message);
            }
        }

        public void Disconnect()
        {
            if (this.sock.Connected)
            {
                this.sock.BeginDisconnect(false, new AsyncCallback(DisconnectCallback), this.sock);
            }
        }

        private void DisconnectCallback(IAsyncResult ar)
        {
            try
            {
                this.sock.EndDisconnect(ar);
                this.sock.Close();
                this.sock = null;
                this.OnDisconnected();
            }
            catch (Exception e)
            {
                this.OnException(e);
            }
        }

        public void Send(string message)
        {
            if (this.sock.Connected)
            {
                byte[] data = Encoding.GetEncoding(Properties.Settings.Default.Encoding).GetBytes(message + "\r\n");
                this.sock.BeginSend(data, 0, data.Length, SocketFlags.None, new AsyncCallback(SendCallback), this.sock);
            }
        }

        private void SendCallback(IAsyncResult ar)
        {
            try
            {
                this.sock.EndSend(ar);
            }
            catch (Exception e)
            {
                this.OnException(e);
            }
        }

        public bool isConnected()
        {
            return (this.sock != null && this.sock.Connected);
        }

        public void Login()
        {
            if (this.serverPassword.Length > 0)
            {
                this.Send("PASS " + this.serverPassword);
            }
            this.Nick(this.nickName);
            this.Send("USER " + this.userName + " 0 * :" + this.realName);

            if (this.nickservPassword.Length > 0)
            {
                this.Privmsg("NickServ", "IDENTIFY " + this.nickservPassword);
            }
        }

        public void Nick(string nick)
        {
            this.nickName = nick;
            this.Send("NICK " + this.nickName);
        }

        public string getNick()
        {
            return this.nickName;
        }

        public void Join(string channel)
        {
            this.Send("JOIN " + channel);
        }

        public void Part(string channel)
        {
            this.Send("PART " + channel);
        }

        public void Notice(string to, string message)
        {
            this.Send("NOTICE " + to + " :" + message);
        }

        public void Privmsg(string to, string message)
        {
            this.Send("PRIVMSG " + to + " :" + message);
        }

        public void Quit(string message)
        {
            this.Send("QUIT :" + message);
        }

        public void Quit()
        {
            this.Send("QUIT :FFXIIRCGateway");
        }

        private void Receive()
        {
            try
            {
                StateObject state = new StateObject();
                state.workSocket = this.sock;
                this.sock.BeginReceive(state.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), state);
            } catch (Exception e) {
                this.OnException(e);
            }
        }

        private void ReceiveCallback(IAsyncResult ar)
        {
            StateObject state = (StateObject)ar.AsyncState;
            int bytesRead = this.sock.EndReceive(ar);

            if (bytesRead <= 0)
            {
                this.Disconnect();
                return;
            }
            else
            {
                state.queue += Encoding.GetEncoding(Properties.Settings.Default.Encoding).GetString(state.buffer, 0, bytesRead);
                int sepPos = state.queue.LastIndexOf("\r\n");
                string[] lines;
                if (sepPos > 0)
                {
                    lines = state.queue.Substring(0, state.queue.LastIndexOf("\r\n")).Replace("\r\n", "\n").Split('\n');
                }
                else
                {
                    lines = state.queue.Replace("\r\n", "\n").Split('\n');
                }
                string lastpart = state.queue.Substring(state.queue.LastIndexOf("\r\n") + 1);
                state.queue = lastpart;
                
                for (int i = 0; i < lines.Length; i++) {
                    if (lines[i].Length < 1) continue;
                    string[] lineArr = lines[i].Split(' ');
                    
                    IRCMessage message = new IRCMessage();
                    message.rawmessage = lines[i];
                    message.rawmessageArr = lineArr;

                    if (lines[i].Substring(0, 1) == ":")
                    {
                        lines[i] = lines[i].Substring(1);
                        lineArr[0] = lineArr[0].Substring(1);

                        message.from = lineArr[0];
                        int pos1 = message.from.IndexOf("!");
                        int pos2 = message.from.IndexOf("@");
                        if (pos1 != -1 && pos2 != -1)
                        {
                            message.nick = message.from.Substring(0, pos1);
                            message.ident = message.from.Substring(pos1 + 1, pos2 - pos1 - 1);
                            message.host = message.from.Substring(pos2 + 1);
                        }
                        message.type = lineArr[1];
                        if (lines[i].IndexOf(" :") != -1)
                        {
                            message.message = lines[i].Substring(lines[i].IndexOf(" :") + 2);
                            message.messageArr = message.message.Split(' ');
                        }

                        if (message.type == "PRIVMSG" ||
                            message.type == "NOTICE" ||
                            message.type == "JOIN" ||
                             message.type == "PART" ||
                            message.type == "QUIT")
                        {
                            message.channel = lineArr[2];
                        }
                        else if (message.type == "INVITE")
                        {
                            message.channel = message.message;
                        }

                        this.MessageHandler(message);
                    }
                    else
                    {
                        message.type = lineArr[0];
                        this.MessageHandler(message);
                    }
                }


                this.sock.BeginReceive(state.buffer, 0, StateObject.BufferSize,
                    SocketFlags.None, new AsyncCallback(ReceiveCallback), state);
            }
        }

        private void ReceiveProcess(string line)
        {
            string[] cmd = line.Split(new char[] { ' ' }, 2);

        }

        private void MessageHandler(IRCMessage message)
        {
            switch (message.type)
            {
                case "PING":
                    this.Send("PONG " + message.rawmessage.Substring(6));
                    break;
                case "PRIVMSG":
                    if (message.message.Substring(0, 1) == ((char)0x01).ToString() &&
                        message.message.Substring(message.message.Length - 1) == ((char)0x01).ToString())
                    {
                        this.CTCPHandler(message);
                        this.OnCTCP(message);
                    }
                    else
                    {
                        this.OnPRIVMSG(message);
                    }
                    break;
                case "NOTICE":
                    this.OnNOTICE(message);
                    break;
                case "JOIN":
                    this.OnJOIN(message);
                    break;
                case "PART":
                    this.OnPART(message);
                    break;
                case "QUIT":
                    this.OnQUIT(message);
                    break;
                case "MODE":
                    this.OnMODE(message);
                    break;
                case "TOPIC":
                    this.OnTOPIC(message);
                    break;
                case "INVITE":
                    this.OnINVITE(message);
                    break;
                case "NICK":
                    this.OnNICK(message);
                    break;
                case "001": // RPL_WELCOME
                    this.OnLoginComplete();
                    break;
                case "002": // RPL_YOURHOST
                    break;
                case "003": // RPL_CREATED
                case "004": // RPL_MYINFO
                case "005": // RPL_
                case "254": // RPL_LUSERCHANNELS
                    break;
                case "265": // RPL_LOCALUSERS
                    break;
                case "266": // RPL_GLOBALUSERS
                    break;
                case "372": // RPL_MOTD
                case "375": // RPL_MOTDSTART
                    break;
                case "376": // RPL_ENDOFMOTD
                    break;
                case "432": // ERR_ERRONEUSNICKNAME
                    this.Nick("fig");
                    break;
                case "433": // ERR_NICKNAMEINUSE
                    this.Nick(this.nickName + "_");
                    break;
                case "464": // ERR_PASSWDMISMATCH
                    break;
            }

        }

        private void CTCPHandler(IRCMessage message)
        {
            string[] cmds = message.message.Substring(1, message.message.Length - 2).Split(new char[] { ' ' }, 2);
            if (cmds.Length < 1) return;
            if (cmds[0] == "VERSION")
            {
                this.Notice(message.nick, ((char)0x01).ToString() + "VERSION FFXIIRCGateway " + System.Windows.Forms.Application.ProductVersion + ((char)0x01).ToString());
            }
            else if (cmds[0] == "PING" && cmds.Length == 2)
            {
                this.Notice(message.nick, ((char)0x01).ToString() + "PING " + cmds[1] + ((char)0x01).ToString() + ((char)0x01).ToString());
            }
            else if (cmds[0] == "TIME")
            {
                this.Notice(message.nick, ((char)0x01).ToString() + "TIME " + DateTime.Now.ToString() + ((char)0x01).ToString());
            }
            else if (cmds[0] == "CLIENTINFO")
            {
                this.Notice(message.nick, ((char)0x01).ToString() + "CLIENTINFO VERSION PING TIME CLIENTINFO" + ((char)0x01).ToString());
            }

        }

        protected virtual void OnConnected()
        {
            if (this.Connected != null)
            {
                this.Connected(this);
            }
        }

        protected virtual void OnDisconnected()
        {
            if (this.Disconnected != null)
            {
                this.Disconnected(this);
            }
        }

        protected virtual void OnConnectFailed(string message)
        {
            Debug.WriteLine("[IRCClient.OnConnectFailed] " + message);
            if (this.ConnectFailed != null)
            {
                this.ConnectFailed(this, message);
            }
        }

        protected virtual void OnException(Exception e)
        {
            Debug.WriteLine("[IRCClient.OnException] " + e.Message);
            if (this.Exception != null)
            {
                this.Exception(this, e);
            }
        }

        protected virtual void OnLoginComplete()
        {
            if (this.LoginComplete != null)
            {
                if (this.syncObject != null && this.syncObject.InvokeRequired)
                {
                    this.syncObject.Invoke(this.LoginComplete, new object[] { this });
                }
                else
                {
                    this.LoginComplete(this);
                }
            }
        }

        protected virtual void OnPRIVMSG(IRCMessage message)
        {
            if (this.PRIVMSG != null)
            {
                if (this.syncObject != null && this.syncObject.InvokeRequired)
                {
                    this.syncObject.Invoke(this.PRIVMSG, new object[] { this, message });
                }
                else
                {
                    this.PRIVMSG(this, message);
                }
            }
        }

        protected virtual void OnNOTICE(IRCMessage message)
        {
            if (this.NOTICE != null)
            {
                if (this.syncObject != null && this.syncObject.InvokeRequired)
                {
                    this.syncObject.Invoke(this.NOTICE, new object[] { this, message });
                }
                else
                {
                    this.NOTICE(this, message);
                }
            }
        }

        protected virtual void OnJOIN(IRCMessage message)
        {
            if (this.JOIN != null)
            {
                if (this.syncObject != null && this.syncObject.InvokeRequired)
                {
                    this.syncObject.Invoke(this.JOIN, new object[] { this, message });
                }
                else
                {
                    this.JOIN(this, message);
                }
            }
        }

        protected virtual void OnPART(IRCMessage message)
        {
            if (this.PART != null)
            {
                if (this.syncObject != null && this.syncObject.InvokeRequired)
                {
                    this.syncObject.Invoke(this.PART, new object[] { this, message });
                }
                else
                {
                    this.PART(this, message);
                }
            }
        }

        protected virtual void OnQUIT(IRCMessage message)
        {
            if (this.QUIT != null)
            {
                if (this.syncObject != null && this.syncObject.InvokeRequired)
                {
                    this.syncObject.Invoke(this.QUIT, new object[] { this, message });
                }
                else
                {
                    this.QUIT(this, message);
                }
            }
        }

        protected virtual void OnMODE(IRCMessage message)
        {
            if (this.MODE != null)
            {
                if (this.syncObject != null && this.syncObject.InvokeRequired)
                {
                    this.syncObject.Invoke(this.MODE, new object[] { this, message });
                }
                else
                {
                    this.MODE(this, message);
                }
            }
        }

        protected virtual void OnTOPIC(IRCMessage message)
        {
            if (this.TOPIC != null)
            {
                if (this.syncObject != null && this.syncObject.InvokeRequired)
                {
                    this.syncObject.Invoke(this.TOPIC, new object[] { this, message });
                }
                else
                {
                    this.TOPIC(this, message);
                }
            }
        }

        protected virtual void OnINVITE(IRCMessage message)
        {
            if (this.INVITE != null)
            {
                if (this.syncObject != null && this.syncObject.InvokeRequired)
                {
                    this.syncObject.Invoke(this.INVITE, new object[] { this, message });
                }
                else
                {
                    this.INVITE(this, message);
                }
            }
        }

        protected virtual void OnNICK(IRCMessage message)
        {
            if (this.NICK != null)
            {
                if (this.syncObject != null && this.syncObject.InvokeRequired)
                {
                    this.syncObject.Invoke(this.NICK, new object[] { this, message });
                }
                else
                {
                    this.NICK(this, message);
                }
            }
        }

        protected virtual void OnCTCP(IRCMessage message)
        {
            if (this.CTCP != null)
            {
                if (this.syncObject != null && this.syncObject.InvokeRequired)
                {
                    this.syncObject.Invoke(this.CTCP, new object[] { this, message });
                }
                else
                {
                    this.CTCP(this, message);
                }
            }
        }
    }

    public class IRCMessage
    {
        public string rawmessage;
        public string[] rawmessageArr;
        public string from;
        public string nick;
        public string ident;
        public string host;
        public string type;
        public string message;
        public string[] messageArr;
        public string channel;
    }

    public class StateObject
    {
        // Client socket.
        public Socket workSocket = null;
        // Size of receive buffer.
        public const int BufferSize = 1024;
        // Receive buffer.
        public byte[] buffer = new byte[BufferSize];
        // Received data string.
        //public StringBuilder sb = new StringBuilder();
        public String queue;
    }


}
